package com.yixiu.bi.redis;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import org.jboss.netty.util.internal.ConcurrentHashMap;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;


public class RedisUtil {

    private org.slf4j.Logger logger = LoggerFactory.getLogger(this.getClass());
    // 配置文件对应的Redis工具
    private static ConcurrentHashMap<String, RedisUtil> redisMap = new ConcurrentHashMap<String, RedisUtil>();
    private String configFile;
    // 连接池
    private JedisPool pool = null;

    // region 初始化



    /**
     * 根据配置文件获取redis
     * @param fileName 配置文件
     * @return
     */
    public static RedisUtil getRedisUtil(String fileName){

        RedisUtil redisUtil = redisMap.get(fileName);
        if(redisUtil == null){
            synchronized(RedisUtil.class){
                redisUtil = redisMap.get(fileName);
                if(redisUtil == null){
                    redisUtil = new RedisUtil(fileName);
                    redisMap.put(fileName, redisUtil);
                }
            }
        }

        return redisUtil;
    }

    /**
     * 根据配置文件初始化
     * @param configFile
     */
    private RedisUtil(String configFile) {
        this.configFile = configFile;

        try {
            Properties properties = new Properties();
            properties.load(RedisUtil.class.getResourceAsStream(configFile));

            String ip = properties.getProperty("redis.host");
            if (StringUtils.isEmpty(ip)) {
                logger.error(configFile + "没有配置redis.host");
                return;
            }

            // 读取 redis.port
            String port = properties.getProperty("redis.port");
            if (StringUtils.isEmpty(port)) {
                logger.error(configFile + "没有配置redis.port");
                return;
            }

            // 构建pool
            JedisPoolConfig config = new JedisPoolConfig();
            String maxTotal = properties.getProperty("setMaxTotal");
            if (!StringUtils.isEmpty(maxTotal)) {
                config.setMaxTotal(Integer.valueOf(maxTotal));
            }

            // TODO  待验证
            Class<?> classConfig  = Class.forName("redis.clients.jedis.JedisPoolConfig");
            // 通过反射获取其他的配置
            Iterator iterator = properties.keySet().iterator();
            while (iterator.hasNext()) {
                if(iterator.next().toString().startsWith("set")) {
                    String methodName  = iterator.next().toString(); // 方法名
                    String value = properties.getProperty(methodName);
                    Method method = classConfig.getMethod(methodName);
                    method.invoke(config,value);
                }
            }

            pool = new JedisPool(config, ip, Integer.valueOf(port));

        } catch (IOException ex) {
            logger.error("配置文件不存在:" + configFile, ex);
        } catch (Exception ex) {
            logger.error("创建RedisUtil异常:" + configFile, ex);
            this.pool = null;
        }
    }


    //endregion


    // region 0 公共操作

    /**
     * 删除一个对象
     */
    public boolean delete(String key){
        boolean ret = false;
        Jedis jedis = pool.getResource();
        try{
            if(jedis.del(key) == 1){
                ret = true;
            }
        }catch(Exception e){
            logger.error("redis 删除" + key + "异常:" + e.getMessage() , e);
        }finally{
            jedis.close();
        }
        return ret;
    }

    /**
     * 搜索keys
     * @param keyPattern key模式符
     * @return 返回搜索到的所有key
     */
    public List<String> getKeys(String keyPattern){
        Jedis jedis = pool.getResource();

        try {
            Set set = jedis.keys(keyPattern);
            List<String> list = new ArrayList<String>(set);
            return list;
        } catch (Exception e) {
            logger.error("redis操作失败:" + e, e);
        }finally{
            jedis.close();
        }
        return  null;
    }

    // endregion 0 公共操作




    // region 1 操作 redis string（字符串）

    /**
     * 设置String  类型的 key - value 键值对
     * 可以设置过期时间
     * @param key 键
     * @param value 值
     * @param expireType key 过期类型，0:永不过期;  1:多少秒后过期;  2:在什么时间点过期 unix时间戳;
     * @param expire 针对不同expireType，传递不同的值; 当expireType=0时可以传任意值
     * @return 设置成功返回true,失败返回false
     */
    public boolean put(String key, String value,int expireType,long expire){
        boolean flag = false;
        if(key == null){
            return flag;
        }
        Jedis jedis = pool.getResource();
        try {
            jedis.set(key, value); // 设置值

            switch(expireType){
                case 1:
                    jedis.expire(key, (int)expire);  // 设置多少秒后过期
                    break;
                case 2:
                    jedis.expireAt(key, expire);   // 设置在什么时间过期  unix时间戳
                    break;
                default:
                    break;
            }
            flag = true;
        } catch (Exception e) {
            logger.error("redis操作异常:" + e.getMessage() , e);
        }finally{
           jedis.close(); // 关闭连接
        }
        return flag;
    }

    /**
     * 设置String  类型的 key - value 键值对
     * 永不过期
     * @param key 键
     * @param value 值
     * @return  设置成功返回true,失败返回false
     */
    public boolean put(String key, String value){
        return put(key,value,0,0);
    }

    /**
     * 获取key对应的value
     * @param key 键
     * @return 返回key对应的值
     */
    public String get(String key){
        String ret = "";
        if(key == null){
            return "";
        }
        Jedis jedis = pool.getResource();
        try {
            ret = jedis.get(key);
        } catch (Exception e) {

            logger.error("redis操作异常:" + e.getMessage() , e);
        }finally{
            jedis.close();
        }
        return ret;
    }


    // endregion   1 操作 redis string（字符串）


    // region 2 操作 redis hash（哈希）



    // region 针对hash 单个字段操作

    /**
     * 设置hash  类型的 key - value 键值对
     * 可以设置过期时间
     * @param key 键
     * @param field  hash的字段
     * @param value  hash的字段值
     * @param expireType key 过期类型，0:永不过期;  1:多少秒后过期;  2:在什么时间点过期 unix时间戳;
     * @param expire 针对不同expireType，传递不同的值; 当expireType=0时可以传任意值
     * @return 设置成功返回true,失败返回false
     */
    public   boolean putHash(String key, String field, String value , int expireType ,long expire){
        boolean ret = false;
        if( StringUtils.isEmpty(field) ){
            return ret;
        }
        Jedis jedis = pool.getResource();
        try {
            jedis.hset(key,field,value);
            switch(expireType){
                case 1:
                    jedis.expire(key, (int)expire);
                    break;
                case 2:
                    jedis.expireAt(key, expire);
                    break;
                default:
                    break;
            }
            ret = true;
        } catch (Exception e) {

            logger.error("redis操作异常:" + e.getMessage(), e);
        }finally{
            jedis.close();
        }
        return ret;
    }

    /**
     * 设置hash  类型的 key - value 键值对
     * 可以设置过期时间
     * @param key 键
     * @param field  hash的字段
     * @param value  hash的字段值
     * @return 设置成功返回true,失败返回false
     */
    public   boolean putHash(String key, String field, String value) {
        return putHash(key, field, value, 0, 0);
    }


    /**
     * 取hash表一个key的一个filed的值
     * @param key 键
     * @param filed hash表中的字段名
     * @return 返回hash表中字段值
     */
    public String getHashFieldValue(String key,String filed){
        String ret = "";
        Jedis jedis = pool.getResource();
        try {
            ret = jedis.hget( key ,filed);
        } catch (Exception e) {
            logger.error("redis操作失败:" + e .getMessage() , e);
        }finally{
            jedis.close();
        }
        return ret;
    }


    // endregion 针对hash 单个字段操作


    // region 针对hashMap 和 对象 操作

    /**
     * 设置hash  类型的 key - value 键值对
     * 可以设置过期时间
     * @param key 键
     * @param map  HashMap对象
     * @param expireType key 过期类型，0:永不过期;  1:多少秒后过期;  2:在什么时间点过期 unix时间戳;
     * @param expire 针对不同expireType，传递不同的值; 当expireType=0时可以传任意值
     * @return 设置成功返回true,失败返回false
     */
    public   boolean putHash(String key, Map map , int expireType ,long expire){
        boolean ret = false;
        if(map == null ){
            return ret;
        }
        Jedis jedis = pool.getResource();
        try {
            jedis.hmset(key,map );
            switch(expireType){
                case 1:
                    jedis.expire(key, (int)expire);
                    break;
                case 2:
                    jedis.expireAt(key, expire);
                    break;
                default:
                    break;
            }
            ret = true;
        } catch (Exception e) {

            logger.error("redis操作异常:" + e.getMessage(), e);
        }finally{
            jedis.close();
        }
        return ret;
    }

    /**
     * 设置hash  类型的 key - value 键值对
     * 可以设置过期时间
     * @param key 键
     * @param bean   bean对象
     * @param expireType key 过期类型，0:永不过期;  1:多少秒后过期;  2:在什么时间点过期 unix时间戳;
     * @param expire 针对不同expireType，传递不同的值; 当expireType=0时可以传任意值
     * @return 设置成功返回true,失败返回false
     */
    public   boolean putHash(String key, Object bean , int expireType ,long expire) {
        try {
            Map map = BeanUtils.describe(bean);
            return putHash(key,map,expireType,expire);
        }
        catch (Exception ex) {
            logger.error("redis操作异常:" + ex.getMessage(), ex);
            return  false;
        }
    }

    /**
     * 设置 hash  类型的 key - value 键值对
     * 设置永不过期
     * @param key 键
     * @param bean   bean对象
     * @return 设置成功返回true,失败返回false
     */
    public   boolean putHash(String key, Object bean) {
        return  putHash(key,bean);
    }

    /**
     * 获取key对应的hash键值对
     * @param key
     * @return  返回HashMap对象
     */
    public Map getHashMap(String key){
        Jedis jedis = pool.getResource();
        try {
            return jedis.hgetAll(key);
        } catch (Exception e) {
            logger.error("redis操作失败:" + e, e);
        }finally{
            jedis.close();
        }
        return null;
    }

    /**
     * 获取bean对象
     * @param key 键
     * @param clazz bean类
     * @return  返回bean对象
     */
    public Object getHashBean(String key,Class clazz){
        Jedis jedis = pool.getResource();
        try {
            Map map =  getHashMap(key);
            Object bean = clazz.newInstance();
            BeanUtils.populate(bean, map);
            return bean;
        } catch (Exception e) {
            logger.error("redis操作失败:" + e.getMessage() , e);
        }finally{
            jedis.close();
        }
        return null;
    }


    // endregion 针对hashMap 和 对象 操作


    // region   针对hash计数

    /**
     * 针对hash的某个字段 增加值
     * 可以设置过期时间
     * @param key 键
     * @param field   字段
     * @param value   数值，64位有符号整数
     * @param expireType key 过期类型，0:永不过期;  1:多少秒后过期;  2:在什么时间点过期 unix时间戳;
     * @param expire 针对不同expireType，传递不同的值; 当expireType=0时可以传任意值
     * @return 设置成功返回true,失败返回false
     */
    public boolean increaseHash(String key, String field, long value,int expireType,long expire){
        boolean ret = false;
        if(StringUtils.isEmpty(key) || StringUtils.isEmpty(field) ){
            return ret;
        }
        Jedis jedis = pool.getResource();
        try {
            jedis.hincrBy(key, field, value);
            switch(expireType){
                case 1:
                    jedis.expire(key, (int)expire);
                    break;
                case 2:
                    jedis.expireAt(key, expire);
                    break;
                default:
                    break;
            }
            ret = true;
        } catch (Exception e) {
            logger.error("redis操作失败:" + e, e);
        }finally{
            jedis.close();
        }
        return ret;
    }


    /**
     * 针对hash的某个字段 增加值
     * 可以设置过期时间
     * @param key 键
     * @param field   字段
     * @param value   浮点数
     * @param expireType key 过期类型，0:永不过期;  1:多少秒后过期;  2:在什么时间点过期 unix时间戳;
     * @param expire 针对不同expireType，传递不同的值; 当expireType=0时可以传任意值
     * @return 设置成功返回true,失败返回false
     */
    public boolean increaseHashFloat(String key, String field, double value,int expireType,long expire){
        boolean ret = false;
        if(StringUtils.isEmpty(key) || StringUtils.isEmpty(field) ){
            return ret;
        }
        Jedis jedis = pool.getResource();
        try {
            jedis.hincrByFloat(key, field, value);
            switch(expireType){
                case 1:
                    jedis.expire(key, (int)expire);
                    break;
                case 2:
                    jedis.expireAt(key, expire);
                    break;
                default:
                    break;
            }
            ret = true;
        } catch (Exception e) {

            logger.error("redis操作失败:" + e, e);
        }finally{
           jedis.close();
        }
        return ret;
    }


    // endregion 针对hash计数


    // endregion   2 操作 redis hash（哈希）



    // region 3  操作 redis list（列表）


    /**
     * 像列表中添加元素
     * @param key 主键
     * @param values 值
     * @return 添加元素的个数
     */
    public Long putList(String key,String ... values){
        if(StringUtils.isEmpty(key) || values.length == 0) {
            return  0L;
        }
        Jedis jedis = pool.getResource();
        try{
            Long res = jedis.lpush(key,values);
            return  res;
        } catch (Exception e) {
            logger.error("redis操作失败:" + e, e);
        }finally{
            jedis.close();
        }
        return 0L;
    }


    public List<String> getList(String key){

        List<String> valueList = new ArrayList<String>();
        Jedis jedis = pool.getResource();

        try{
            valueList.addAll(jedis.lrange(key, 0,-1));
        } catch (Exception e) {

            logger.error("redis操作失败:" + e, e);
        }finally{
            jedis.close();
        }

        return valueList;
    }



    // region   通过list实现动态时间窗

    /**
     * 动态时间窗计数，用于查询一个时间段内key出现的次数，比如10分钟出现多少次
     *
     * 实现思路：
     *(1)记录
     * 每30分钟为一个时间片段，使用list记录key出现的时间戳;
     * 新key为 key + 当前时间(0分到29分记录为0,30分到59分记录为30)
     *(2)统计
     * 将 second 时间段覆盖的list片段都取出来，和当前时间比较，凡是在 second 秒内的都计数一次,最后返回总次数
     *
     *
     * 具体步骤：
     * 比如现在要统计10分钟,key出现了多少次
     * (1)假设2018-01-01 01:01:00时key出现 ，当前时间在0分到30分之间，取0分，新key为 key:2018-01-01-01-00
     * 将当前时间戳插入list[1514739660]
     * (2)假设2018-01-01 01:02:00时key又一次出现 ，当前时间在0分到30分之间，取0分，新key为 key:2018-01-01-01-00
     * 将当前时间戳插入list[1514739660,1514739720]
     * (3)假设2018-01-01 01:11:00时key又一次出现 ，当前时间在0分到30分之间，取0分，新key为 key:2018-01-01-01-00
     * 将当前时间戳插入list[1514739660,1514739720,1514740260]
     * (4)假设2018-01-01 01:12:00时要统计最近10分钟key出现多少次
     * 根据second=10*60计算出来覆盖的片段只有key:2018-01-01-01-00
     * 将list所有元素item取出来，当(now-item)<=10分钟,就计数一次
     * 第1个元素不满足，因此10分钟内只有2次访问。
     *
     *
     * @param key 要统计的key
     * @param second  要统计的时间段，单位是秒，通过这个字段设置key的生存周期，统计时会在key的生存周期内统计次数；
     *         比如要统计10分钟就传10*60,统计一个小时就传60*60
     * @return 计数成功返回true，失败返回false
     */
    public boolean LPushTimestamp(String key,int second){
        boolean ret = false;

        Date now = new Date();
        long timestamp = now.getTime();
        String dateString ;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm");

        if( StringUtils.isEmpty(key)){
            return ret;
        }

        // 根据当前时间，计算list对应的key, 0分一个key,30分一个key
        Calendar cal=Calendar.getInstance();
        cal.setTime(now);
        if ( cal.get(cal.MINUTE) >= 30 ){
            cal.set(cal.MINUTE, 30);
        }else{
            cal.set(cal.MINUTE, 0);
        }
        cal.set(cal.SECOND, 0);
        cal.set(cal.MILLISECOND, 0);

        dateString = sdf.format(cal.getTime());
        key = key+":"+dateString;

        // 计算key要保留的生命周期，至少会保留2个时间片
        int section = (int)(Math.ceil(second/1800D)) + 1 ; // math.ceil(x)返回大于等于参数x的最小整数
        cal.add(cal.MINUTE, section * 30);
        long  timestamp_expireAt = (int)(cal.getTime().getTime()/1000);

        Jedis jedis = pool.getResource();

        try{
            jedis.lpush(key, String.valueOf(timestamp));  // 将当前的时间戳加入列表
            jedis.expireAt(key, timestamp_expireAt);      // 设置过期时间
            ret = true;
        } catch (Exception e) {
            logger.error("redis操作失败:" + e, e);
        }finally{
           jedis.close();
        }
        return ret;
    }


    /**
     * 统计 key 在某一个时间段内出现的次数
     *
     * 具体思路查看方法LPushTimestamp的说明。
     *
     * @param key 要统计的key
     * @param second 时间段，单位是秒
     * @return 返回出现的次数
     */
    public int LGetTimestampCount(String key,int second){
        int ret = 0;
        List<String> list = new ArrayList<String>();
        Date now = new Date();
        long timestamp = now.getTime() - second * 1000;// 获取second秒之前的时间戳
        List<String> keysList = new ArrayList<String>();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm");
        String dateString;

        if( StringUtils.isEmpty(key)){
            return ret;
        }

        // 根据当前时间，计算list对应的key, 0分一个key,30分一个key
        Calendar cal=Calendar.getInstance();
        cal.setTime(now);
        cal.set(cal.SECOND, 0);
        cal.set(cal.MILLISECOND, 0);
        if ( cal.get(cal.MINUTE) >= 30 ){
            cal.set(cal.MINUTE, 30);
        }else{
            cal.set(cal.MINUTE, 0);
        }

        // 计算section秒内包含哪些时间片
        int section = (int)(Math.ceil(second/1800D)) + 1 ;
        for ( int i = 0 ; i < section ; i++){
            if ( i != 0 ){
                cal.add(cal.MINUTE, -30);
            }
            dateString = sdf.format(cal.getTime());
            keysList.add(key + ":" + dateString);
        }

        Jedis jedis = pool.getResource();

        try{
            // 取出来所有的时间片
            for (String k : keysList){
                list.addAll(jedis.lrange(k, 0,-1));
            }
            // 凡是大于等于timestamp的时间戳都是second秒内的记录
            for (String s : list){
                if (timestamp <= Long.valueOf(s)) {
                    ret ++;
                }
            }
        } catch (Exception e) {
            logger.error("redis操作失败:" + e, e);
        }finally{
            jedis.close();
        }
        return ret;
    }


    //  endregion   通过list实现动态时间窗





    // endregion  操作 redis list（列表）

    // region 4 操作 redis set（集合）

    /**
     *  向集合中添加元素
     * 可以设置过期时间
     * @param key 键
     * @param expireType key 过期类型，0:永不过期;  1:多少秒后过期;  2:在什么时间点过期 unix时间戳;
     * @param expire 针对不同expireType，传递不同的值; 当expireType=0时可以传任意值
     * @param value  要加入集合的元素
     * @return 设置成功返回true,失败返回false
     */
    public boolean putSet(String key,int expireType,long expire,String ... value){
        boolean ret = false;
        if(StringUtils.isEmpty(key) || value.length == 0){
            return ret;
        }
        Jedis jedis = pool.getResource();
        try {
            jedis.sadd(key, value);
            switch(expireType){
                case 1:
                    jedis.expire(key, (int)expire);
                    break;
                case 2:
                    jedis.expireAt(key, expire);
                    break;
                default:
                    break;
            }
            ret = true;
        } catch (Exception e) {
            logger.error("redis操作失败:" + e, e);
        }finally{
            jedis.close();
        }
        return ret;
    }

    /**
     * 获取集合的元素
     * @param key 集合对应的key
     * @return 返回集合元素
     */
    public Set<String> getSet(String key){
        Set<String> ret = new HashSet<String>();
        if(StringUtils.isEmpty(key)){
            return ret;
        }
        Jedis jedis = pool.getResource();
        try {
            ret = jedis.smembers(key);
        } catch (Exception e) {
            logger.error("redis操作失败:" + e, e);
        }finally{
           jedis.close();
        }
        return ret;
    }

    /**
     * 获取集合中元素的个数
     * @param key 集合对应的key
     * @return 返回集合元素的个数
     */
    public Long getSetCount(String key){
        Long ret = 0L;
        if(StringUtils.isEmpty(key)){
            return ret;
        }
        Jedis jedis = pool.getResource();
        try {
            ret = jedis.scard(key);
        } catch (Exception e) {
            logger.error("redis操作失败:" + e, e);
        }finally{
            jedis.close();
        }
        return ret;
    }


    // endregion  操作 redis set（集合）


    // region 5  操作 redis zset(sorted set：有序集合)


    // endregion  操作 redis zset(sorted set：有序集合)

}

